""" Definitions of the actions 
"""
from name import SyntacticName, FreeName, PrivateName
from pi.name import Match, GlobalName

class AtomicAction:
    """ A base class for atomic actions
    """
    def __init__(self):
        raise NotImplementedError("abstract class")
    
    def pre(self, nc, g, i):
        """ Test if an action can be perform
        @param nc: Name context
        @param g: Guard
        @param i: Thread id
        @return: True if it can be activated, otherwise return False
        """
        raise NotImplementedError("abstract method")
    
    def post(self, nc, g, i):
        """ Perform the action
        @param nc: Name context
        @param g: Guard
        @param i: Thread id
        @return: name context after performing the action
        """
        raise NotImplementedError("abstract method")
    

class Silence(AtomicAction):
    """ An internal action
    """
    def __init__(self):
        pass
    
    def pre(self, nc, g, i):
        """ Precondition
        """
        return nc.check(nc.substitute(g, i))
    
    def post(self, nc, g, i):
        """ Compute new context
        """
        return nc.eval(nc.substitute(g, i))
    
    def __str__(self):
        return "tau"
    
    def __repr__(self):
        return "tau"


class ObservableAction(AtomicAction):
    """ An observable action
    """
    def __init__(self, channel, name):
        if not isinstance(channel, SyntacticName):
            raise Exception("Channel name must be a SyntacticName")
        self._chan = channel
        self._name = name
        
    def channel(self):
        """ Get the channel name
        """
        return self._chan
    
    def name(self):
        """ Get the send-name or a place holder 
        """
        return self._name
    
    def pre(self, nc, g, i):
        if not nc.check(nc.substitute(g, i)):
            return False
        nnc = nc.eval(nc.substitute(g, i))
        c = nnc.instance(self._chan, i)
        return not isinstance(c, PrivateName)
       

class Input(ObservableAction):
    """ An action of receiving information on a channel
        and put it into the place holder
    """
    def __init__(self, chan, name):
        """
        @param chan: input channel name
        @param name: place holder name
        """
        ObservableAction.__init__(self, chan, name)
        if isinstance(name, str):
            # convert string to FreeName
            self._name = FreeName(name)
        else:
            self._name = name
    
    def post(self, nc, g, i):
        """ Perform action and return a new name context
        """
        nnc = nc.eval(nc.substitute(g, i))
        iname = nnc.nextInputName()
        return nnc.instantiate(self._name, iname, i)
        
    def __str__(self):
        return "%s(%s)" % (str(self._chan), str(self._name))
    
    def __repr__(self):
        return "%s(%s)" % (repr(self._chan), repr(self._name))


class Output(ObservableAction):
    """ An output action
    """
    def __init__(self, chan, name):
        ObservableAction.__init__(self, chan, name)
        if not isinstance(name, SyntacticName):
            raise Exception("Output name must be a SyntacticName")
        
    def post(self, nc, g, i):
        """ Perform action and return a new name context
        """
        nnc = nc.eval(nc.substitute(g, i))
        oname = nnc.instance(self._name, i)
        # Bound output
        if isinstance(oname, PrivateName):
            new_oname = nnc.nextOutputName()
            return nnc.exchange(oname, new_oname)
        # Output (normal)
        return nnc
    
    def __str__(self):
        return "%s<%s>" % (str(self._chan), str(self._name))

    def __repr__(self):
        return "%s<%s>" % (repr(self._chan), repr(self._name))


class GuardedAction:
    """ A guarded action
    """
    def __init__(self, guard, action):
        self._guard = guard
        self._action = action
    
    def guard(self):
        return self._guard
    
    def action(self):
        return self._action
    
    def __str__(self):
        return "%s%s" % (self._guard, self._action)

    def __repr__(self):
        return "%r%r" % (self._guard, self._action)


class StrlType(object):
    """ A possibility of thread moving.
    """
    SPWN = 0
    MONO = 1
    TERM = 2
    NORM = 3
    
    def __init__(self, stype):
        self._type = stype

    def isTerm(self):
        """ Test if target is a terminal node """
        return self._type == StrlType.TERM
     
    def isSpwn(self):
        """ Test if target is a spawning node """
        return self._type == StrlType.SPWN
     
    def isMono(self):
        """ Test if target is a mono node """
        return self._type == StrlType.MONO
     
    def isNorm(self):
        """ Test if target is a normal node """
        return self._type == StrlType.NORM
    
    def stype(self):
        return self._type
    
    
class Rewrite(object):
    """ Base class for transitions in pi-graph
    """
    
    def __init__(self):
        raise NotImplementedError("abstract class")
    
    def pre(self, nc, grp):
        """ Test if the transition can be performed
        @param nc: Name context
        @param grp: A pi-graph
        @return: True if the transition can be activated, otherwise False
        """
        raise NotImplementedError("abstract method")
    
    def post(self, nc, cc, grp):
        """ Perform the transition
        @param nc: Name context
        @param cc: Control context
        @param grp: A pi-graph
        @return: Context after performing the transition
        """
        raise NotImplementedError("abstract method")
    

class AtomicRewrite(Rewrite, StrlType):
    """ A transition that is performed by an atomic action
    """
    def __init__(self, rname, gaction, i, src, target, stype):
        """
        @param rname: replicator name
        @param gaction: guarded action
        @param i: the thread will be moved
        @param source: source node
        @param target: target node
        @param spawn, term, mono: properties of the moving
        """
        StrlType.__init__(self, stype)
        self._r = rname
        self._ga = gaction
        self._i = i
        self._source = src
        self._target = target
    
    def thread(self):
        """ Get thread id that will be moved """
        return self._i
    
    def target(self):
        """ Get target node id """
        return self._target


    def source(self):
        """ Get source node id """
        return self._source
        
    def getGuardedAction(self):
        """ Get guarded action. """
        return self._ga
    
    def guard(self):
        """ Get the guard """
        return self._ga.guard()
    
    def action(self):
        """ Get the action """
        return self._ga.action()
    
    def rname(self):
        """ Get replicator name """
        return self._r
    
    def changeThread(self, i):
        """ Create a new atomic transition with given thread.
        """
        if i == self._i:
            return self
        return AtomicRewrite(self._r, self._ga,
                                i, self._source, self._target, self._type)

    def priv(self, nc, grp):
        ''' Check if it a is private action in given context.
        @precondition: thread is not None and satisfy the guard
        '''
        nnc = nc
        c = self.action().channel()
        if isinstance(c, GlobalName):
            cc = nnc.instance(c)
        elif self.isMono() or self.isSpwn():
            nnc = nnc.init(self._r, self._i, grp)
            cc = nnc.instance(c, self._i)
        else:
            cc = nnc.instance(c, self._i)            
        return isinstance(cc, PrivateName)
    
    def pre(self, nc, grp):
        """ Test if it can be activated
        """
        a = self._ga.action()
        g = self._ga.guard()
        nnc = nc
        if self.isMono() or self.isSpwn():
            nnc = nc.init(self._r, self._i, grp)
        return a.pre(nnc, g, self._i)
    
    def post(self, nc, cc, grp):
        """ Compute new context after activating
        """
        nnc = nc
        a = self._ga.action()
        g = self._ga.guard()
        # compute new name context
        if self.isMono():
            nnc = nnc.init(self._r, self._i, grp)
            nnc = a.post(nnc, g, self._i)
            nnc = nnc.reset(self._r, self._i)
        elif self.isSpwn():
            nnc = nnc.init(self._r, self._i, grp)
            nnc = a.post(nnc, g, self._i)
        elif self.isTerm():
            nnc = a.post(nnc, g, self._i)
            nnc = nnc.reset(self._r, self._i)
        else:
            nnc = a.post(nnc, g, self._i)
        # return new standardized context
        return nnc.normalize(), cc.rewrite(self)
        
    def __str__(self):
        if self._source is None:
            if self._i is not None:
                return "<%s, %s, %d, r->%d>" % (self._r,
                        self._ga, self._i, self._target)
            else:
                return "<%s, %s@%d>" % (self._r,
                        self._ga, self._target)
        else:
            return "<%s, %s, %d, %d->%d>" % (self._r,
                    self._ga, self._i, self._source, self._target)

    def __repr__(self):
        return repr(self._ga)
    
    
class ComRules(object):
    """ All communication rules
    """
    def __init__(self):
        raise NotImplementedError("abstract class")
    
    def com(self, nc, io, gao, ii, gai):
        """ Normal communication
        """
        go = gao.guard()
        gi = gai.guard()
        co = gao.action().channel()
        ci = gai.action().channel()
        no = gao.action().name()  # out-name
        ni = gai.action().name()
        #
        NC1 = nc.eval(nc.substitute(go, io))         
        NC2 = NC1.eval(NC1.substitute(gi, ii))
        cco = NC2.instance(co, io)  # instance of out-channel
        cci = NC2.instance(ci, ii)  # instance of in-channel
        nno = NC2.instance(no, io)  # instance of out-name
        NC3 = NC2.refine(Match(cco, cci))
        return NC3.instantiate(ni, nno, ii)
    
    def syncMono_Mono(self, nc, ro, io, gao, ri, ii, gai, grp):
        """ Communication between two mono actions
        """
        NC = nc.init(ro, io, grp)
        NC = NC.init(ri, ii, grp)
        NC = self.com(NC, io, gao, ii, gai)
        NC = NC.reset(ro, io)
        NC = NC.reset(ri, ii)
        return NC
    
    def syncMono_Spaw(self, nc, ro, io, gao, ri, ii, gai, grp):
        """ Communication between mono and spawn actions
        """
        NC = nc.init(ro, io, grp)
        NC = NC.init(ri, ii, grp)
        NC = self.com(NC, io, gao, ii, gai)
        NC = NC.reset(ro, io)
        return NC
    
    def syncMono_Term(self, nc, ro, io, gao, ri, ii, gai, grp):
        """ Communication between mono and terminal actions
        """
        NC = nc.init(ro, io, grp)
        NC = self.com(NC, io, gao, ii, gai)
        NC = NC.reset(ro, io)
        NC = NC.reset(ri, ii)
        return NC
    
    def syncMono_Norm(self, nc, ro, io, gao, ri, ii, gai, grp):
        """ Communication between mono and normal actions
        """
        NC = nc.init(ro, io, grp)
        NC = self.com(NC, io, gao, ii, gai)
        NC = NC.reset(ro, io)
        return NC
    
    def syncSpaw_Mono(self, nc, ro, io, gao, ri, ii, gai, grp):
        """ Communication between spawn and mono actions
        """
        NC = nc.init(ro, io, grp)
        NC = NC.init(ri, ii, grp)
        NC = self.com(NC, io, gao, ii, gai)
        NC = NC.reset(ri, ii)
        return NC
    
    def syncSpaw_Spaw(self, nc, ro, io, gao, ri, ii, gai, grp):
        """ Communication between spawn actions
        """
        NC = nc.init(ro, io, grp)
        NC = NC.init(ri, ii, grp)
        NC = self.com(NC, io, gao, ii, gai)
        return NC
    
    def syncSpaw_Term(self, nc, ro, io, gao, ri, ii, gai, grp):
        """ Communication between spawn and terminal actions
        """
        NC = nc.init(ro, io, grp)
        NC = self.com(NC, io, gao, ii, gai)
        NC = NC.reset(ri, ii)
        return NC
    
    def syncSpaw_Norm(self, nc, ro, io, gao, ri, ii, gai, grp):
        """ Communication between spawn and normal actions
        """
        NC = nc.init(ro, io, grp)
        NC = self.com(NC, io, gao, ii, gai)
        return NC
    
    def syncTerm_Mono(self, nc, ro, io, gao, ri, ii, gai, grp):
        """ Communication between terminal and mono actions
        """
        NC = nc.init(ri, ii, grp)
        NC = self.com(NC, io, gao, ii, gai)
        NC = NC.reset(ro, io)
        NC = NC.reset(ri, ii)
        return NC
    
    def syncTerm_Spaw(self, nc, ro, io, gao, ri, ii, gai, grp):
        """ Communication between terminal and spawn actions
        """
        NC = nc.init(ri, ii, grp)
        NC = self.com(NC, io, gao, ii, gai)
        NC = NC.reset(ro, io)
        return NC
    
    def syncTerm_Term(self, nc, ro, io, gao, ri, ii, gai, grp):
        """ Communication between terminal actions
        """
        NC = self.com(nc, io, gao, ii, gai)
        NC = NC.reset(ro, io)
        NC = NC.reset(ri, ii)
        return NC
    
    def syncTerm_Norm(self, nc, ro, io, gao, ri, ii, gai, grp):
        """ Communication between terminal and normal actions
        """
        NC = self.com(nc, io, gao, ii, gai)
        NC = NC.reset(ro, io)
        return NC
    
    def syncNorm_Mono(self, nc, ro, io, gao, ri, ii, gai, grp):
        """ Communication between normal and mono actions
        """
        NC = nc.init(ri, ii, grp)
        NC = self.com(NC, io, gao, ii, gai)
        NC = NC.reset(ri, ii)
        return NC
    
    def syncNorm_Spaw(self, nc, ro, io, gao, ri, ii, gai, grp):
        """ Communication between normal and spawn actions
        """
        NC = nc.init(ri, ii, grp)
        NC = self.com(NC, io, gao, ii, gai)
        return NC
    
    def syncNorm_Term(self, nc, ro, io, gao, ri, ii, gai, grp):
        """ Communication between normal and terminal actions
        """
        NC = self.com(nc, io, gao, ii, gai)
        NC = NC.reset(ri, ii)
        return NC
    
    
class SyncRewrite(Rewrite, ComRules):
    """ A synchronization consists of a pair: output and input guarded actions.
    """
    def __init__(self, atomic_out, atomic_in):
        self._ao = atomic_out
        self._ai = atomic_in
        
    def atomicIn(self):
        """ Get the in-atomic-transition.
        """
        return self._ai
    
    def atomicOut(self):
        """ Get the out-atomic-transition.
        """
        return self._ao
        
    def pre(self, nc, grp):
        """ Check if it can be performed.
        """
        io = self._ao.thread()  # id of thread
        ii = self._ai.thread()
        if io is None or ii is None:
            return False
        # initialize mono/spawn nodes
        NC = nc
        if self._ao.isMono() or self._ao.isSpwn():
            NC = NC.init(self._ao.rname(), io, grp)
        if self._ai.isMono() or self._ai.isSpwn():
            NC = NC.init(self._ai.rname(), ii, grp)
            
        if not NC.check(NC.substitute(self._ao.guard(), io)):
            return False
        NC1 = NC.eval(NC.substitute(self._ao.guard(), io))        
        if not NC1.check(NC1.substitute(self._ai.guard(), ii)):
            return False
        NC2 = NC1.eval(NC1.substitute(self._ai.guard(), ii))

        co = NC2.instance(self._ao.action().channel(), io)  # instance of out-channel
        ci = NC2.instance(self._ai.action().channel(), ii)  # instance of in-channel
        po = NC2.partitions().val(co)  # partition id of out-channel
        pi = NC2.partitions().val(ci)  # partition id of in-channel
        return not NC2.distinctions().distinct(po, pi)
    
    def post(self, nc, cc, grp):
        """ Perform a sync
        """
        io = self._ao.thread()  # thread-out
        ii = self._ai.thread()  # thread-in
        ro = self._ao.rname()
        ri = self._ai.rname()
        gai = self._ai.getGuardedAction()
        gao = self._ao.getGuardedAction()

        if self._ao.isMono() and self._ai.isMono():
            nnc = self.syncMono_Mono(nc, ro, io, gao, ri, ii, gai, grp)
        elif self._ao.isMono() and self._ai.isSpwn():
            nnc = self.syncMono_Spaw(nc, ro, io, gao, ri, ii, gai, grp)
        elif self._ao.isMono() and self._ai.isTerm():      
            nnc = self.syncMono_Term(nc, ro, io, gao, ri, ii, gai, grp)
        elif self._ao.isMono() and self._ai.isNorm():  
            nnc = self.syncMono_Norm(nc, ro, io, gao, ri, ii, gai, grp)
        elif self._ao.isSpwn() and self._ai.isMono():
            nnc = self.syncSpaw_Mono(nc, ro, io, gao, ri, ii, gai, grp)
        elif self._ao.isSpwn() and self._ai.isSpwn():
            nnc = self.syncSpaw_Spaw(nc, ro, io, gao, ri, ii, gai, grp)
        elif self._ao.isSpwn() and self._ai.isTerm():
            nnc = self.syncSpaw_Term(nc, ro, io, gao, ri, ii, gai, grp)
        elif self._ao.isSpwn() and self._ai.isNorm():
            nnc = self.syncSpaw_Norm(nc, ro, io, gao, ri, ii, gai, grp)
        elif self._ao.isTerm() and self._ai.isMono():
            nnc = self.syncTerm_Mono(nc, ro, io, gao, ri, ii, gai, grp)
        elif self._ao.isTerm() and self._ai.isSpwn():
            nnc = self.syncTerm_Spaw(nc, ro, io, gao, ri, ii, gai, grp)
        elif self._ao.isTerm() and self._ai.isTerm():
            nnc = self.syncTerm_Term(nc, ro, io, gao, ri, ii, gai, grp)
        elif self._ao.isTerm() and self._ai.isNorm():
            nnc = self.syncTerm_Norm(nc, ro, io, gao, ri, ii, gai, grp)
        elif self._ao.isNorm() and self._ai.isMono():
            nnc = self.syncNorm_Mono(nc, ro, io, gao, ri, ii, gai, grp)
        elif self._ao.isNorm() and self._ai.isSpwn():
            nnc = self.syncNorm_Spaw(nc, ro, io, gao, ri, ii, gai, grp)
        elif self._ao.isNorm() and self._ai.isTerm():
            nnc = self.syncNorm_Term(nc, ro, io, gao, ri, ii, gai, grp)
        elif self._ao.isNorm() and self._ai.isNorm():
            nnc = self.com(nc, io, gao, ii, gai)
        # Generate new control context
        return nnc.normalize(), cc.rewrite(self)
    
    def __str__(self):
        return "[%s|%s]" % (self._ao, self._ai)

    def __repr__(self):
        return "[%r|%r]" % (self._ao, self._ai)